{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:23.104486Z",
     "start_time": "2025-02-09T03:56:20.955503Z"
    }
   },
   "source": [
    "# 导入库\n",
    "import matplotlib as mpl\n",
    "# Matplotlib 绘制的图形会直接嵌入到 Notebook 的输出单元格中，而不是弹出一个独立的窗口\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "\n",
    "# os库提供了一种使用操作系统相关功能的便捷方式，允许与操作系统进行交互\n",
    "import os\n",
    "\n",
    "# sys库主要用于处理Python运行时的环境相关信息以及与解释器的交互\n",
    "import sys\n",
    "import time\n",
    "\n",
    "# tqdm库是一个快速，可扩展的Python进度条，可以在 Python 长循环中添加一个进度提示信息，用户只需要封装任意的迭代器 tqdm(iterator)，即可获得一个进度条显示迭代进度。\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "\n",
    "# torch.nn是PyTorch中用于构建神经网络的模块\n",
    "import torch.nn as nn\n",
    "\n",
    "# torch.nn.functional是PyTorch中包含了神经网络的一些常用函数\n",
    "import torch.nn.functional as F\n",
    "\n",
    "# Python 中的一个属性，用于获取当前 Python 解释器的版本信息。它返回一个命名元组（named tuple）\n",
    "print(sys.version_info)\n",
    "\n",
    "# 遍历模块，打印模块名称和版本信息，快速检查当前环境中安装的某些常用 Python 库的版本信息\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "# 判断是否有 GPU，如果有，则使用 GPU 进行训练，否则使用 CPU 进行训练\n",
    "# torch.cuda.is_available()用于判断是否有GPU\n",
    "# torch.device(\"cuda:0\")创建一个 PyTorch 设备对象，表示使用第一个 GPU（索引为 0）\n",
    "# torch.device(\"cpu\")创建一个 PyTorch 设备对象，表示使用 CPU\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.3.1+cu121\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 数据准备",
   "id": "8d3a035da6b77b68"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:23.847189Z",
     "start_time": "2025-02-09T03:56:23.105490Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 提供了许多常用的公开数据集\n",
    "from torchvision import datasets\n",
    "\n",
    "# 导入 ToTensor 类，它是一个图像变换工具，用于将 PIL 图像或 NumPy 数组转换为 PyTorch 张量（torch.Tensor）\n",
    "# 同时将像素值从 [0, 255] 缩放到 [0.0, 1.0]\n",
    "# 在加载数据集时，通常需要将图像转换为张量，以便输入到神经网络中\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# 图像预处理工具，用于对图像进行各种变换（如缩放、裁剪、旋转、归一化等）\n",
    "from torchvision import transforms\n",
    "from torch.utils.data import random_split\n",
    "\n",
    "# PyTorch 中用于定义图像预处理流水线的方式\n",
    "# transforms.Compose 将多个图像变换操作组合在一起，按顺序依次应用到图像上\n",
    "# 传入一个空列表 []，则表示不进行任何变换,定义一个空的变换流水线\n",
    "transform = transforms.Compose([ToTensor()])\n",
    "\n",
    "# 训练集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载训练集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# 测试集\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载测试集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 这里用 random_split 按照 11 : 1 的比例来划分数据集\n",
    "# torch.Generator().manual_seed(seed) 设置随机种子，保证划分结果一致\n",
    "train_ds, val_ds = random_split(train_ds, [55000, 5000], torch.Generator().manual_seed(seed))"
   ],
   "id": "daa3b5bc1bda4d2b",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.070786Z",
     "start_time": "2025-02-09T03:56:23.848192Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 计算数据集中所有图像的均值和标准差\n",
    "# ds：数据集对象，通常是一个可迭代对象，返回元组 (img, label)\n",
    "def cal_mean_std(ds):\n",
    "    # 初始化均值和标准差的累加器\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "\n",
    "    # 对每个图像的每个通道计算均值，然后对所有图像求平均\n",
    "\n",
    "    # 遍历每张图片,_ 是标签（未使用）\n",
    "    for img, _ in ds:\n",
    "        # 计算每张图片的均值和方差,dim=(1, 2) 表示在高度和宽度维度上计算均值。\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "\n",
    "    # 将累加的均值和标准差除以数据集大小，得到整个数据集的均值和标准差\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))"
   ],
   "id": "bf47e2fba09b3fe6",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2856]), tensor([0.3202]))\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.075375Z",
     "start_time": "2025-02-09T03:56:27.071291Z"
    }
   },
   "cell_type": "code",
   "source": [
    "img, label = train_ds[0]\n",
    "img.shape, label"
   ],
   "id": "cb8f020a949a3d62",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(torch.Size([1, 28, 28]), 9)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.080379Z",
     "start_time": "2025-02-09T03:56:27.076380Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data.dataloader import DataLoader\n",
    "\n",
    "batch_size = 32\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=4)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ],
   "id": "9b2533155acfbdbb",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 模型定义",
   "id": "635510a2ccba05a2"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "功能: 定义了一个卷积神经网络（CNN）模型，用于图像分类任务。\n",
    "\n",
    "原理:\n",
    "\n",
    "使用卷积层提取图像的局部特征。\n",
    "\n",
    "使用池化层降低特征图的尺寸。\n",
    "\n",
    "使用全连接层将提取的特征映射到最终的类别数。\n",
    "\n",
    "作用:\n",
    "\n",
    "适用于图像分类任务（如 MNIST、CIFAR-10 等）。\n",
    "\n",
    "能够自动提取图像的特征，并进行分类。\n",
    "\n",
    "为什么这样做:\n",
    "\n",
    "卷积神经网络是图像分类任务中最常用的模型之一。\n",
    "\n",
    "通过参数初始化和多层卷积操作，增强模型的表达能力和训练稳定性。"
   ],
   "id": "1520b1d45d28038f"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.086739Z",
     "start_time": "2025-02-09T03:56:27.080379Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 CNN 的类，继承自 nn.Module\n",
    "class CNN(nn.Module):\n",
    "\n",
    "    # 定义类的构造函数，接受一个参数 activation，表示激活函数的类型，默认值为 \"relu\"\n",
    "    # 初始化模型的层结构和参数\n",
    "    def __init__(self, activation=\"relu\"):\n",
    "        super().__init__()\n",
    "\n",
    "        # 根据 activation 参数选择激活函数，F.relu 或 F.selu\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "\n",
    "        # 定义两个卷积层，输入通道数分别为 1 和 32，输出通道数均为 32，卷积核大小为 3x3，填充为 1。\n",
    "        # 卷积层用于提取图像的局部特征,通过卷积操作提取图像的底层特征\n",
    "        # 提取输入图像的初级特征，输出通道数增加到 32\n",
    "        # 输入：(batch_size, 1, 28, 28)，输出：(batch_size, 32, 28, 28)\n",
    "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)\n",
    "        # 输入：(batch_size, 32, 28, 28)，输出：(batch_size, 32, 28, 28)\n",
    "        # 进一步提取特征，通道数保持不变\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1)\n",
    "\n",
    "        # 定义一个最大池化层，池化核大小为 2x2，步幅为 2\n",
    "        # 池化层用于降低特征图的空间维度，减少计算量\n",
    "        # 通过池化操作减少特征图的尺寸，同时保留重要特征\n",
    "        # 输入：(batch_size, 32, 28, 28)，输出：(batch_size, 32, 14, 14)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        # 定义两个卷积层，输入通道数分别为 32 和 64，输出通道数均为 64，卷积核大小为 3x3，填充为 1。\n",
    "        # 卷积层用于提取图像的局部特征,通过卷积操作提取图像的中层特征\n",
    "        # 输入：(batch_size, 32, 14, 14)，输出：(batch_size, 64, 14, 14)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n",
    "        # 输入：(batch_size, 64, 14, 14)，输出：(batch_size, 64, 14, 14)\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1)\n",
    "\n",
    "        # 输入：(batch_size, 64, 14, 14)，输出：(batch_size, 64, 7, 7)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        # 定义两个卷积层，输入通道数分别为 64 和 128，输出通道数均为 128，卷积核大小为 3x3，填充为 1。\n",
    "        # 卷积层用于提取图像的局部特征,通过卷积操作提取图像的高层特征\n",
    "        # 输入：(batch_size, 64, 7, 7)，输出：(batch_size, 128, 7, 7)\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 输入：(batch_size, 128, 7, 7)，输出：(batch_size, 128, 7, 7)\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1)\n",
    "\n",
    "        # 输入：(batch_size, 128, 7, 7)，输出：(batch_size, 128, 3, 3)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        # 定义一个展平层，用于将多维特征图展平为一维向量\n",
    "        self.flatten = nn.Flatten()\n",
    "\n",
    "        # 定义一个全连接层，输入维度为 128 * 3 * 3，输出维度为 128。\n",
    "        # 全连接层用于将提取的特征映射到高维空间\n",
    "        self.fc1 = nn.Linear(128 * 3 * 3, 128)\n",
    "\n",
    "        # 输出层将特征映射到最终的类别数\n",
    "        self.fc2 = nn.Linear(128, 10)\n",
    "\n",
    "        # 调用 init_weights 方法初始化模型参数\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    #  定义前向传播方法，接受输入数据 x，并获取激活函数 act\n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "\n",
    "        # 将输入数据 x 依次通过 conv1、激活函数、conv2、激活函数和池化层\n",
    "        # 提取图像的底层特征，并通过池化层降低特征图的尺寸。减少计算量。\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "\n",
    "        # 将降低尺寸后的特征图依次通过 conv3、激活函数、conv4、激活函数和池化层\n",
    "        # 提取图像的中层特征，并通过池化层降低特征图的尺寸。减少计算量。\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "\n",
    "        # 将降低尺寸后的特征图依次通过 conv5、激活函数、conv6、激活函数和池化层\n",
    "        # 提取图像的高层特征，并通过池化层降低特征图的尺寸。减少计算量。\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "\n",
    "        # 将降低尺寸后的特征图展平为一维向量，输入全连接层\n",
    "        x = self.flatten(x)\n",
    "\n",
    "        # 将展平后的特征向量通过全连接层 fc1 和激活函数。\n",
    "        x = act(self.fc1(x))\n",
    "\n",
    "        # 将特征向量通过全连接层 fc2，得到最终的输出\n",
    "        x = self.fc2(x)\n",
    "        return x"
   ],
   "id": "55461365260db39b",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.094137Z",
     "start_time": "2025-02-09T03:56:27.086739Z"
    }
   },
   "cell_type": "code",
   "source": [
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")  # 打印模型的参数信息"
   ],
   "id": "173c7ecf8b190e4e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 288\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 147456\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.314229Z",
     "start_time": "2025-02-09T03:56:27.094137Z"
    }
   },
   "cell_type": "code",
   "source": [
    "activation = \"relu\"\n",
    "model = CNN(activation)\n",
    "model.to(device)\n",
    "img = torch.randn(1, 1, 28, 28).to(device)\n",
    "model(img)"
   ],
   "id": "53957406e5034207",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[-0.1220,  0.0194,  0.0106, -0.0171, -0.0892, -0.0747, -0.0886, -0.1283,\n",
       "          0.0574, -0.0040]], device='cuda:0', grad_fn=<AddmmBackward0>)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 模型变体",
   "id": "3c656be45ed08dbc"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "#练习不同尺寸的卷积核，padding，stride的效果\n",
    "class CNN1(nn.Module):\n",
    "    def __init__(self, activation=\"relu\"):\n",
    "        super().__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        #输入通道数，图片是灰度图，所以是1，图片是彩色图，就是3，输出通道数，就是卷积核的个数（32,1,28,28）\n",
    "        # paddind=2 卷积核的边缘补2个0，stride=2 步长为2\n",
    "        # 输入(batch_size,1,28,28) 输出(batch_size,32,14,14)\n",
    "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=5, padding=2, stride=2)\n",
    "        # 输入(batch_size,32,14,14) 输出(batch_size,32,14,14)\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,32,14,14) 输出(batch_size,32,7,7)\n",
    "        self.pool = nn.MaxPool2d(2, 2)  #池化核大小为2（2*2），步长为2\n",
    "\n",
    "        # 输入(batch_size,32,7,7) 输出(batch_size,64,7,7)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,64,7,7) 输出(batch_size,64,7,7)\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,64,7,7) 输出(batch_size,128,7,7)\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,128,7,7) 输出(batch_size,128,7,7)\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,128,7,7) 输出(batch_size,128,3,3)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        self.flatten = nn.Flatten()\n",
    "\n",
    "        self.fc1 = nn.Linear(128 * 3 * 3, 128)\n",
    "        self.fc2 = nn.Linear(128, 10)  #输出尺寸（32,10）\n",
    "\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # 定义激活函数\n",
    "        act = self.activation\n",
    "        # 两次卷积，一次池化，两次卷积，一次池化，展平，全连接，输出\n",
    "        x = act(self.conv1(x))\n",
    "        x = act(self.conv2(x))\n",
    "        x = self.pool(x)\n",
    "\n",
    "        x = act(self.conv3(x))\n",
    "        x = act(self.conv4(x))\n",
    "        x = self.pool(x)\n",
    "\n",
    "        x = act(self.conv5(x))\n",
    "        x = act(self.conv6(x))\n",
    "        x = self.pool(x)\n",
    "\n",
    "        x = self.flatten(x)\n",
    "        x = act(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n"
   ],
   "id": "752742e471a3fa17"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "目的: 可视化 PyTorch 模型的计算图，帮助理解模型的结构和数据流动。\n",
    "\n",
    "步骤:\n",
    "\n",
    "创建虚拟输入张量。\n",
    "\n",
    "运行模型，得到输出。\n",
    "\n",
    "使用 make_dot 生成计算图。\n",
    "\n",
    "将计算图保存为 PNG 文件。\n",
    "\n",
    "输出: 生成一个名为 model_CNN.png 的图像文件，包含模型的计算图。"
   ],
   "id": "ca1ff14603858c4b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:28.043709Z",
     "start_time": "2025-02-09T03:56:27.314229Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从 torchviz 库中导入 make_dot 函数\n",
    "# make_dot 用于生成 PyTorch 模型的计算图\n",
    "from torchviz import make_dot\n",
    "\n",
    "# 创建一个虚拟输入张量，形状为 (1, 1, 28, 28)\n",
    "# 需要一个输入张量来运行模型并生成计算图\n",
    "dummy_input = torch.randn(1, 1, 28, 28).to(device)\n",
    "\n",
    "# 将虚拟输入张量传递给模型，得到输出\n",
    "# 计算图是基于模型的前向传播过程生成的，因此需要运行模型\n",
    "output = model(dummy_input).to(device)\n",
    "\n",
    "# 生成模型的计算图\n",
    "# output: 模型的输出张量\n",
    "# params: 模型的参数（通过 model.named_parameters() 获取）\n",
    "# make_dot 使用输出张量和模型参数来构建计算图\n",
    "dot = make_dot(output, params=dict(model.named_parameters()))\n",
    "\n",
    "# 生成一个名为 model_CNN.png 的文件\n",
    "dot.render(\"model_CNN\", format=\"png\")"
   ],
   "id": "9fa1e9435257f8ea",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'model_CNN.png'"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 模型训练",
   "id": "e08bc96e8c6ba14"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:03.800069Z",
     "start_time": "2025-02-09T03:58:01.119475Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir,\n",
    "                                    flush_secs=flush_secs)  # 实例化SummaryWriter, log_dir是log存放路径，flush_secs是每隔多少秒写入磁盘\n",
    "\n",
    "    def draw_model(self, model, input_shape):  #graphs\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))  # 画模型图\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "        )  # 画loss曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )  # 画acc曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "        )  # 画lr曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss,把loss，val_loss取掉，画loss曲线\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)  # 画loss曲线\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)  # 画acc曲线\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)  # 画lr曲线\n"
   ],
   "id": "bf751873e58d2379",
   "outputs": [],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:05.803611Z",
     "start_time": "2025-02-09T03:58:05.799385Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 SaveCheckpointsCallback 的类。\n",
    "# 该类用于在训练过程中保存模型的检查点。封装保存检查点的逻辑，方便在训练过程中调用\n",
    "class SaveCheckpointsCallback:\n",
    "    \"\"\"\n",
    "    Callback to save model checkpoints during training.\n",
    "    \"\"\"\n",
    "\n",
    "    #定义类的构造函数，接受以下参数：\n",
    "    # save_dir: 检查点保存的目录。\n",
    "    # save_step: 每隔多少步保存一次检查点，默认值为 500。\n",
    "    # save_best_only: 是否只保存性能最好的检查点，默认值为 True\n",
    "    # 原理：初始化回调类的参数和状态\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "\n",
    "        # 将传入的参数保存为类的属性\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "\n",
    "        # 初始化一个变量 best_metrics，用于记录当前最好的性能指标值，初始值为 -1\n",
    "        # 原理: 用于比较当前模型的性能是否优于之前保存的模型,当 save_best_only 为 True 时，只保存性能最好的模型\n",
    "        self.best_metrics = -1\n",
    "\n",
    "        # 检查保存目录是否存在，如果不存在则创建该目录\n",
    "        # os.path.exists()用于判断路径是否存在, os.mkdir()用于创建目录\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    # 定义 __call__ 方法，使类的实例可以像函数一样调用。参数包括：\n",
    "    # step: 当前训练的步数。\n",
    "    # state_dict: 模型的状态字典（包含模型参数）。\n",
    "    # metric: 当前模型的性能指标值（如验证集准确率），默认为 None\n",
    "    # 在训练过程中定期调用该方法，保存检查点\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "\n",
    "        # 检查当前步数是否是 save_step 的倍数，如果不是则直接返回\n",
    "        # 控制保存检查点的频率，避免频繁保存\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        # 检查是否只保存性能最好的检查点\n",
    "        # 根据 save_best_only 的值决定保存策略,提供两种保存策略：定期保存和只保存最佳模型\n",
    "        if self.save_best_only:\n",
    "\n",
    "            # 如果 save_best_only 为 True，则要求 metric 不能为 None。\n",
    "            # 原理: 使用 assert 断言确保 metric 有值。如果 metric 为 None，无法判断模型性能是否更好，因此需要提前检查\n",
    "            assert metric is not None\n",
    "\n",
    "            # 检查当前模型的性能指标是否优于之前保存的最佳模型\n",
    "            # 只保存性能更好的模型，避免保存性能下降的模型\n",
    "            if metric >= self.best_metrics:\n",
    "                # 将模型的状态字典保存到指定目录下的 best.ckpt 文件中\n",
    "                # 使用 torch.save 保存模型参数。保存当前性能最好的模型，方便后续加载和使用\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "\n",
    "                # 更新 best_metrics 为当前模型的性能指标值\n",
    "                self.best_metrics = metric\n",
    "\n",
    "        # 如果 save_best_only 为 False，则执行以下逻辑\n",
    "        # 定期保存检查点，不关心模型性能是否更好\n",
    "        else:\n",
    "\n",
    "            # 将模型的状态字典保存到指定目录下，文件名为当前步数\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))"
   ],
   "id": "68efe425b70740a3",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:10.414222Z",
     "start_time": "2025-02-09T03:58:10.410769Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 EarlyStopCallback 的类.用于监控模型在验证集上的性能，并在性能不再提升时触发早停。\n",
    "# 封装早停的逻辑，方便在训练过程中调用\n",
    "class EarlyStopCallback:\n",
    "\n",
    "    # 定义类的构造函数，接受以下参数\n",
    "    # patience: 容忍的轮次数，默认值为 5。如果在 patience 轮内性能没有提升，则触发早停。\n",
    "    # min_delta: 性能提升的最小阈值，默认值为 0.01。只有当性能提升超过该阈值时，才认为模型有改进。\n",
    "    # 初始化早停回调的参数和状态\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "\n",
    "        # 将传入的参数保存为类的属性\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "\n",
    "        self.best_metric = -1  # 用于比较当前模型的性能是否优于之前的最佳性能\n",
    "\n",
    "        # 初始化一个计数器 counter，用于记录性能没有提升的连续轮次数，初始值为 0\n",
    "        # 当性能没有提升时，计数器增加；当性能提升时，计数器重置,用于判断是否达到了早停的条件\n",
    "        self.counter = 0\n",
    "\n",
    "    # 训练过程中定期调用该方法，更新早停状态\n",
    "    def __call__(self, metric):\n",
    "\n",
    "        # 检查当前性能指标是否比之前的最佳性能提升了至少 min_delta\n",
    "        # 避免微小的波动触发早停,当性能有显著提升时，才认为模型有改进\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "\n",
    "            self.best_metric = metric  # 记录当前最佳性能\n",
    "            self.counter = 0  # 性能有提升时，重置计数器,重新开始计算性能没有提升的连续轮次数\n",
    "\n",
    "        # 性能没有提升时，增加计数器\n",
    "        else:\n",
    "            self.counter += 1  # 记录性能没有提升的连续轮次数\n",
    "\n",
    "    # 定义一个只读属性 early_stop，用于判断是否触发早停。\n",
    "    # 使用 @property 装饰器将方法转换为属性。方便外部代码通过属性访问早停状态。\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "\n",
    "        # 检查计数器 counter 是否大于或等于 patience\n",
    "        # 提供早停的触发条件\n",
    "        return self.counter >= self.patience"
   ],
   "id": "e1260d4d3d629a34",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:31.841849Z",
     "start_time": "2025-02-09T03:58:31.788848Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 这是一个装饰器，用于禁用梯度计算。\n",
    "# 在评估模型时，我们不需要计算梯度，因为不会进行反向传播和参数更新。禁用梯度计算可以减少内存消耗并加速计算。\n",
    "@torch.no_grad()\n",
    "# 定义评估函数，接受三个参数：model（要评估的模型）、dataloader（数据加载器，用于提供评估数据）、loss_fct（损失函数）。\n",
    "# 用于评估模型在给定数据集上的性能。\n",
    "# 通过封装评估过程，可以方便地在不同模型和数据集上重复使用。\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    # 初始化三个空列表，分别用于存储每个批次的损失、预测标签和真实标签。\n",
    "    # 用于累积所有批次的损失和标签，以便后续计算平均损失和准确率。\n",
    "    # 累积所有批次的结果可以更准确地反映模型在整个数据集上的性能。\n",
    "    loss_list = []  # 记录损失\n",
    "    pred_list = []  # 记录预测\n",
    "    label_list = []  # 记录标签\n",
    "\n",
    "    # 遍历数据加载器中的每个批次，datas 是输入数据，labels 是对应的真实标签\n",
    "    for datas, labels in dataloader:\n",
    "        # 将输入数据和标签移动到指定的设备（GPU或CPU），转到GPU可以加速计算\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 将输入数据传递给模型，得到模型的输出 logits（通常是未归一化的预测分数）。\n",
    "        # 模型的前向传播过程，计算输入数据对应的输出。通过模型的输出可以计算损失和预测标签。\n",
    "        logits = model(datas)\n",
    "\n",
    "        # 将当前批次的损失值（转换为 Python 浮点数）添加到 loss_list 中。\n",
    "        # loss.item() 将张量中的单个值提取为 Python 浮点数。\n",
    "        # 累积所有批次的损失值，以便后续计算平均损失。\n",
    "        loss = loss_fct(logits, labels)\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 通过 argmax 函数获取模型预测的类别标签。axis=-1 表示在最后一个维度上取最大值对应的索引。\n",
    "        # logits 通常是每个类别的分数，argmax 找到分数最高的类别作为预测结果。\n",
    "        # 将模型的输出转换为具体的类别标签，便于与真实标签进行比较。\n",
    "        preds = logits.argmax(axis=-1)\n",
    "\n",
    "        # 将预测标签从 GPU 移动到 CPU，并转换为 NumPy 数组，再转换为 Python 列表，然后添加到 pred_list 中。\n",
    "        # preds.cpu().numpy().tolist() 将张量转换为 Python 列表。\n",
    "        # 累积所有批次的预测标签，以便后续计算准确率。\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    #计算预测标签和真实标签之间的准确率\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "\n",
    "    #返回所有批次的平均损失和准确率。\n",
    "    return np.mean(loss_list), acc"
   ],
   "id": "e909466dad00184a",
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:59:33.718557Z",
     "start_time": "2025-02-09T03:59:33.603543Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 training 的函数，用于训练模型。参数包括：\n",
    "# model: 要训练的模型。\n",
    "# train_loader: 训练数据的数据加载器。\n",
    "# val_loader: 验证数据的数据加载器。\n",
    "# epoch: 训练的轮数。\n",
    "# loss_fct: 损失函数。\n",
    "# optimizer: 优化器。\n",
    "# eval_step: 每隔多少步评估一次验证集性能，默认值为 500。\n",
    "# 该函数通过遍历训练数据，更新模型参数，并定期评估验证集性能。封装训练过程，方便重复使用\n",
    "def training(\n",
    "        model,\n",
    "        train_loader,\n",
    "        val_loader,\n",
    "        epoch,\n",
    "        loss_fct,\n",
    "        optimizer,\n",
    "        tensorboard_callback=None,\n",
    "        save_ckpt_callback=None,\n",
    "        early_stop_callback=None,\n",
    "        eval_step=500,\n",
    "):\n",
    "    # 初始化一个字典 record_dict，用于记录训练和验证过程中的损失和准确率。\n",
    "    # 通过记录训练和验证的性能，可以分析模型的训练过程。方便后续绘制训练曲线或分析模型表现。\n",
    "    record_dict = {\n",
    "        \"train\": [],  # \"train\": 存储训练集的损失和准确率。\n",
    "        \"val\": []  # \"val\": 存储验证集的损失和准确率。\n",
    "    }\n",
    "\n",
    "    # 初始化一个全局步数计数器 global_step，用于记录当前训练的步数。\n",
    "    # 步数用于控制何时评估验证集性能。通过步数而不是轮数来控制评估频率，更灵活。\n",
    "    global_step = 0\n",
    "\n",
    "    # 将模型设置为训练模式。在训练模式下，模型会启用一些特定于训练的功能（如 Dropout 和 BatchNorm）。\n",
    "    # 确保模型在训练时行为正确。\n",
    "    model.train()\n",
    "\n",
    "    # 使用 tqdm 创建一个进度条，总长度为 epoch * len(train_loader)，即总训练步数。\n",
    "    # tqdm 是一个进度条库，用于显示训练进度。\n",
    "    # 提供可视化的训练进度，方便监控训练过程。\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "\n",
    "        # 外层循环，遍历每个训练轮次。每个轮次会遍历整个训练数据集一次。\n",
    "        # 多轮训练可以提高模型的性能。\n",
    "        for epoch_id in range(epoch):\n",
    "\n",
    "            # 内层循环，遍历训练数据加载器中的每个批次。\n",
    "            for datas, labels in train_loader:\n",
    "\n",
    "                # 将输入数据和标签移动到指定的设备（GPU或CPU），转到GPU可以加速计算\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 清除优化器中之前的梯度。\n",
    "                # PyTorch 会累积梯度，因此在每次反向传播前需要清除之前的梯度。避免梯度累积导致错误的参数更新。\n",
    "                optimizer.zero_grad()\n",
    "\n",
    "                logits = model(datas)  # 模型前向计算\n",
    "                loss = loss_fct(logits, labels)  # 计算损失\n",
    "\n",
    "                # 计算损失相对于模型参数的梯度。\n",
    "                # 反向传播算法，通过链式法则计算梯度。梯度用于更新模型参数，以最小化损失。\n",
    "                loss.backward()\n",
    "\n",
    "                # 使用优化器更新模型参数。\n",
    "                optimizer.step()\n",
    "\n",
    "                preds = logits.argmax(axis=-1)  # 通过 argmax 函数获取模型预测的类别标签。\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())  # 计算当前批次的准确率\n",
    "                loss = loss.cpu().item()  # 将损失值从 GPU 移动到 CPU，并转换为 Python 浮点数。方便记录和打印损失值。\n",
    "\n",
    "                # 将当前批次的损失、准确率和步数记录到 record_dict[\"train\"] 中\n",
    "                # 累积训练过程中的性能指标。方便后续分析训练过程\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 每隔 eval_step 步评估一次验证集性能。\n",
    "                # 使用 global_step 控制评估频率。定期评估验证集性能，避免过拟合。\n",
    "                if global_step % eval_step == 0:\n",
    "                    # 将模型设置为评估模式\n",
    "                    model.eval()\n",
    "\n",
    "                    # 调用 evaluating 函数计算验证集的损失和准确率\n",
    "                    # 在验证集上评估模型性能。验证集性能反映了模型的泛化能力。\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "\n",
    "                    # 将验证集的损失、准确率和步数记录到 record_dict[\"val\"] 中。\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "\n",
    "                    # 将模型设置为训练模式\n",
    "                    model.train()\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        # 调用 tensorboard 回调函数，记录训练过程中的关键指标\n",
    "                        tensorboard_callback(\n",
    "                            global_step,  # 当前训练的全局步数，用于在 TensorBoard 中标识不同的训练阶段\n",
    "                            loss=loss, val_loss=val_loss,  # 记录训练集和验证集的损失值，用于监控模型在训练和验证集上的表现\n",
    "                            acc=acc, val_acc=val_acc,  # 记录训练集和验证集的准确率，用于监控模型的分类性能\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],  # 取出当前学习率，用于监控学习率的变化，确保学习率调整策略正常工作\n",
    "                        )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # 调用保存模型权重的回调函数，保存当前模型的权重\n",
    "                        save_ckpt_callback(\n",
    "                            global_step,  # 当前训练的全局步数，用于标识保存的模型权重对应的训练阶段\n",
    "                            model.state_dict(),  # 保存模型的当前状态字典（即模型的所有参数），以便后续可以恢复模型\n",
    "                            metric=val_acc  # 使用验证集的准确率作为指标，判断是否保存当前模型（通常只保存性能最好的模型）\n",
    "                        )\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 调用早停回调函数，监控验证集准确率是否不再提升\n",
    "                        early_stop_callback(val_acc)  # 传入验证集准确率，用于判断是否满足早停条件\n",
    "                        if early_stop_callback.early_stop:  # 检查是否触发了早停条件\n",
    "                            # 如果触发了早停条件，打印提示信息并结束训练\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 返回训练记录字典，包含训练过程中的关键指标\n",
    "\n",
    "                # 增加全局步数计数器\n",
    "                # 记录当前训练的步数,控制评估频率和记录训练进度\n",
    "                global_step += 1\n",
    "\n",
    "                # 更新进度条\n",
    "                pbar.update(1)\n",
    "\n",
    "                # 在进度条中显示当前训练的轮次。\n",
    "                # 更新进度条的后缀信息。方便监控训练进度。\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    # 返回记录的训练和验证性能的字典,包含训练和验证过程中的损失和准确率\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "\n",
    "activation = \"relu\"\n",
    "model = CNN(activation)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/cnn-{activation}\")\n",
    "tensorboard_callback.draw_model(model, [1, 1, 28, 28])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/cnn-{activation}\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10)"
   ],
   "id": "647bd9bb695bc591",
   "outputs": [],
   "execution_count": 16
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T04:04:13.474513Z",
     "start_time": "2025-02-09T03:59:47.347568Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=1000\n",
    ")"
   ],
   "id": "81b211c47663873b",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/34380 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "1ef1d31b6f0e4018ae1e1dde26e90559"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 14 / global_step 25000\n"
     ]
    }
   ],
   "execution_count": 17
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T04:04:54.028887Z",
     "start_time": "2025-02-09T04:04:53.917894Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{int(x / 1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "id": "8a0818ea3f4cc42d",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ],
      "image/png": "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Q6dOncyQkYbng7p69aqkpaWZccH6ufTu3VvsMtZXu8/79esns2fPlr/++svndsEe/99//21+du7c2fzUa4HeSfM8/jpcRuPydfx1LPTo0aOlvr7e3IWLjo6+jXsPOyM3kZtcyEvOykuK3NT62opNVFZWSl1dncTGxnq9ruuHDx92r9fW1spTTz0l+/fvN93n8fHxYgd6oVy3bp25cGhX8NKlS+Whhx6SgwcPmrGido7dF00mytf54Pqdy+XLl2X8+PHmolpQUGASjx3o8ADtDu/bt68cPXpUFi1aJOPGjTMX09DQUNvErwkgKytLRo4cKQMHDjSv6TEOCwu7ISn4Ov66np6eLomJibJ+/XrzPqClkJvITS7kJefkJUVuCgy2aej8v3RsZHh4uBk/GRMTI3ahFwoXvROmySUhIUG++OILmTFjhq1jb66pU6dKz549ZceOHebhULt48skn3X8eNGiQOS/uvvtuczctOTnZNvHreGj90tTUcf96t2z48OHmOQrPRAvcTna9PpObmibYr8tOz0uK3BQYbDN0TS+OeiI0rFyh63FxcV4nzp9//inff/+92JneLbjnnnvkyJEjjotduY75f50P6tFHH5WysjKf3cZ2osNG9N+J5zkR7PHPnTtXvv32W3PHT5Oiix5jHTLUsDynr+Ovdw21UtChQ4du237DOchN3pycm8hLzshLitwUOGzT0NEuvaFDh0p+fr5Xt6GuJyUluV+bMGGC6QJ85plnZOPGjWJX2u2r3cJawtJpsSvtFteLhuf5cPHiRVPlxvN8UDpGePny5ebz0QoodnXy5EkzFtrznAjW+HU8vyaSzZs3mzt+erw96bWgXbt2XsdfS3zqswENj7/GnpGRYe4mklDQ0shN3pycm8hL9s5LitwUgCwb2bhxo6lesm7dOuvQoUPWs88+a0VHR1tnzpy5oQKIVryIiIjwqnwRzF588UWrsLDQVCz5+eefrZSUFCsmJsZU/LBr7JcuXTKVWXTR+N59913zZ1dlluXLl5vjv3XrVqusrMxUeunbt69VXV3ts7rLe++9Z0VGRlrFxcVWsMevv3vppZdMFReN88cff7SGDBliJSYmWjU1NUEf/+zZs61OnTqZc/706dPu5Z9//nFvM2vWLKt3797Wjh07rNLSUispKcksLg0r22RlZVmxsbGmAg7QkshNzslN5CXn5iVFbgo8tmroqFWrVpkTKCwszJT03L17t99Sh3l5eeai+tVXX1nBLj093erevbuJOz4+3qwfOXLE1rG7LgYNFy1f6Srl+dprr5kLhH7JSE5OtsrLy93vb3hBVbm5uVZUVJRJyMEcv15UU1NTra5du5pSlgkJCdbMmTPdX6yCPX5fceuydu1a9zb6xeG5556z7rzzTqtDhw7W5MmTTcLxl0zUvHnzzL8jz/MEaAnkJmfkJvKSc/OSIjcFnhD9T2v3KgEAAABAS7LNMzoAAAAA4EJDBwAAAIDt0NABAAAAYDs0dAAAAADYDg0dAAAAALZDQwcAAACA7dDQAQAAAGA7NHQAAAAA2A4NHaCFTJ8+XSZNmtTauwEAgEFegtPR0AEAAABgOzR0gEb68ssvZdCgQdK+fXvp0qWLpKSkyMKFC+WTTz6RrVu3SkhIiFkKCwvN9idOnJAnnnhCoqOjpXPnzjJx4kQ5duzYDXfcli5dKl27dpWOHTvKrFmz5Nq1a60YJQAgWJCXAN/a+nkdgA+nT5+WqVOnyooVK2Ty5Mly6dIlKS4ulqefflqOHz8uFy9elLVr15ptNXlcv35dxowZI0lJSWa7tm3byptvviljx46VsrIyCQsLM9vm5+dLRESESUKabDIzM02yeuutt1o5YgBAICMvAf7R0AEamVBqa2tlypQpkpCQYF7Tu2hK76RdvXpV4uLi3Nt/9tlnUl9fLx9//LG5m6Y04ehdNE0eqamp5jVNLGvWrJEOHTrIgAED5PXXXzd349544w1p04aOVwCAb+QlwD/OVKARBg8eLMnJySaJpKWlyUcffSQXLlzwu/2BAwfkyJEjEhUVJZGRkWbRO2o1NTVy9OhRr79Xk4mL3mm7fPmyGV4AAIA/5CXAP3p0gEYIDQ2V7du3y65du+SHH36QVatWyeLFi2XPnj0+t9ekMHToUPn8889v+J2OewYAoDnIS4B/NHSARtKu/pEjR5olOzvbDBXYvHmz6eavq6vz2nbIkCGSl5cn3bp1Mw9z3uwOW3V1tRlmoHbv3m3usvXq1euWxwMACG7kJcA3hq4BjaB3yN5++20pLS01D3l+/fXXcv78ebn33nulT58+5kHO8vJyqaysNA98Tps2TWJiYkxFG33os6KiwoyBfv755+XkyZPuv1cr2cyYMUMOHTok27Ztk5ycHJk7dy7joAEAN0VeAvyjRwdoBL37VVRUJCtXrjSVbPSuWW5urowbN06GDRtmkoX+1KEBBQUF8sgjj5jtX375ZfOgqFbDiY+PN+OpPe+k6XpiYqKMGjXKPDiqFXSWLFnSqrECAAIfeQnwL8SyLOsmvwdwi+l8BVVVVbJly5bW3hUAAMhLsA36HwEAAADYDg0dAAAAALbD0DUAAAAAtkOPDgAAAADboaEDAAAAwHZo6AAAAACwHRo6AAAAAGyHhg4AAAAA26GhAwAAAMB2aOgAAAAAsB0aOgAAAABsh4YOAAAAALGb/wH58iSip/Hd6AAAAABJRU5ErkJggg=="
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 18
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 评估",
   "id": "d21fef8aa9e3e4f3"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T04:04:59.534896Z",
     "start_time": "2025-02-09T04:04:55.126450Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.load_state_dict(torch.load(f\"checkpoints/cnn-{activation}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, test_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ],
   "id": "810a917a4476d48e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.2110\n",
      "accuracy: 0.9228\n"
     ]
    }
   ],
   "execution_count": 19
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "473e9bf72abd7608"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
